package cn.tedu.charging.order.service;

import cn.tedu.charging.common.pojo.message.ChargingStartCheckMessage;
import cn.tedu.charging.common.pojo.message.DelayCheckMessage;
import cn.tedu.charging.common.pojo.param.OrderAddParam;
import cn.tedu.charging.common.pojo.vo.VehicleVO;
import cn.tedu.charging.common.protocol.JsonResult;
import cn.tedu.charging.common.utils.CronUtil;
import cn.tedu.charging.order.feign.DeviceClient;
import cn.tedu.charging.order.feign.UserClient;
import cn.tedu.charging.order.producer.MqttProducer;
import cn.tedu.charging.order.utils.XxlJobTaskUtil;
import com.alibaba.fastjson2.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.UUID;
import java.util.concurrent.CompletableFuture;

@Service
@Slf4j
public class OrderService {
    @Autowired
    private DeviceClient deviceClient;
    @Autowired
    private UserClient userClient;
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private MqttProducer mqttProducer;

    public String createOrder(OrderAddParam param) {
        /**
         * 1.每一张订单都要生成一个订单号,应该至少满足2个特点:唯一、有序(排序上升,顺序性)
         * 雪花算法,以始终指针为基础,多个含有业务意义的有序字段拼接而成
         * 组成部分(不同雪花算法不一样):
         * a.时间戳(13位)
         * b.数据中心ID(服务运行在的机房)
         * c.机器ID
         * d.本机序列号...
         * 以始终为基础,有可能导致一个情况出现,时钟回拨,机器时钟值上一秒还是1789097****,下一秒1789096****
         * 001789097**** 001789098**** 001789099**** 01178998****
         * 这里还是用UUID替代,简单
         */
        String orderNo = UUID.randomUUID().toString().replaceAll("-", "");
        //2.检查设备:设备是否存在、枪是否是空闲
        CompletableFuture<Void> checkDeviceFuture=CompletableFuture.supplyAsync(()->{
            checkGun(param.getGunId());
            return null;
        });
        //3.检查用户:用户是否存在、用户是否绑定车辆、车辆id信息
        CompletableFuture<Void> checkUserFutrue=CompletableFuture.supplyAsync(()->{
            checkVehicleAndGet(param.getUserId());
            return null;
        });
        //主线程阻塞等待
        CompletableFuture<Void> allFuture = CompletableFuture.allOf(checkDeviceFuture, checkUserFutrue);
        try {
            allFuture.get();
            checkUserFutrue.get();
            checkDeviceFuture.get();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("设备或用户不存在");
        }
        //4.订单发送命令,告知设备枪桩,开始自检充电,物联网通信协议mqtt,emqx
        String startCheckTopic = "/charging/device/check/" + param.getPileId();
        ChargingStartCheckMessage chargingStartCheckMessage = new ChargingStartCheckMessage();
        chargingStartCheckMessage.setOrderNo(orderNo);
        chargingStartCheckMessage.setUserId(param.getUserId());
        chargingStartCheckMessage.setGunId(param.getGunId());
        String messageJson = JSON.toJSONString(chargingStartCheckMessage);
        mqttProducer.sendMessage(startCheckTopic, messageJson);
        //充电桩接到命令,充电桩开始设备自检,然后自检成功才开始充电,并且告知订单可以创建success
        //自检失败,告诉订单创建fail,但是没有逻辑可以保证充电桩的反馈信息一定可以被订单获取
        //5.给足充电桩自检的时间,订单要做兜底检查,到底充电桩反馈信息是什么结果,考虑无响应,延迟60秒检查,rabbitmq延迟消息
        DelayCheckMessage delayCheckMessage = new DelayCheckMessage();
        delayCheckMessage.setOrderNo(orderNo);
        delayCheckMessage.setUserId(param.getUserId());
        delayCheckMessage.setPileId(param.getPileId());
        delayCheckMessage.setGunId(param.getGunId());
        delayCheckMessage.setVehicleId(checkVehicleAndGet(param.getUserId()));
        rabbitTemplate.convertAndSend("DELAY_BILL_EX", "", delayCheckMessage);
        log.debug("延迟消息已成功发送，检查订单是否有结果:{}", delayCheckMessage);
        //6.订单最终要创建一个预估最大充电时长的定时任务，调用xxl-job接口
        Integer maxChargeTime = 1000*60*10;
        String cronExpression = CronUtil.delayCron(maxChargeTime);
        XxlJobTaskUtil.createJobTask(new RestTemplate(), cronExpression, 3, orderNo, param.getUserId());
        return orderNo;
    }

    private Integer checkVehicleAndGet(Integer userId) {
        JsonResult<VehicleVO> vehicleVOJsonResult = userClient.binded(userId);
        VehicleVO data = vehicleVOJsonResult.getData();
        if (data == null || data.getId() == null) {
            throw new RuntimeException("请先绑定车辆");
        }
        return data.getId();
    }

    private void checkGun(Integer gunId) {
        JsonResult<Boolean> booleanJsonResult = deviceClient.checkGun(gunId);
        Boolean data = booleanJsonResult.getData();
        if (!data) {
            throw new RuntimeException("该充电枪暂不可用");
        }
    }
}
